package yagwl.server.fnd;

import java.io.IOException;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import yagwl.service.fnd.flex.ConfigService;
import yagwl.service.util.svc.Context;
import yagwl.service.util.web.UrlUtil;

// generic: should not have dependency on app-engine
public abstract class AbstractFilter implements Filter {

  private static Logger log = Logger.getLogger(AbstractFilter.class.getName());
  protected HttpServletRequest request;
  protected HttpServletResponse response;
  protected String sessionId;
  private FilterConfig fc;

  public void doFilter(ServletRequest req, ServletResponse res,
      FilterChain chain) throws IOException, ServletException {
    request = (HttpServletRequest) req;
    response = (HttpServletResponse) res;
    logRequest();

    if (redirectLocalHostToIP())
      return;

    log.fine("Authorize sender");
    if (!isSenderAuthorized()) {
      log.warning("Sender not authorized");
      response.setHeader("WWW-Authenticate", "MTEP realm=\"Commerce\"");
      response.sendError(401,
          "Require Developer Token in \"Authorization\" header");
      return;
    }

    Context ctx = initializeContextFromCookie();

    if (isUserLoggingOut(ctx)) {
      log.info("Logging out user");
      ctx = new Context(null);
    }

    if (isUserLoggingIn(ctx)) {
      log.info("Logging in user; perform post-login processing");
      setUserIdInContext(ctx);
      log.info("userId: " + ctx.getUserId());
    }

    if (isUserLoggedIn(ctx)) {
      log.info("Logged in user");
      setEmailInContext(ctx);
      setNicknameInContext(ctx);
      log.info("Context: "
          + (String) ctx.getAttribute(Context.Attr.Email.toString()) + " "
          + (String) ctx.getAttribute(Context.Attr.Nickname.toString()) + " "
          + ctx.getUserId());
    }

    if (isUserIdMismatch(ctx)) {
      log.info("User ids out of sync: " + ctx.getUserId());
      ctx = new Context(null);
    }

    if (ctx.isSessionUpdated()) {
      log.info("Session updated; setting cookie");
      setSessionCookie(ctx);
    }

    log.fine("Authorize request");
    if (!isRequestAuthorized()) {
      log.warning("Request not authorized");
    }

    chain.doFilter(req, res);
  }

  /**
   * sender to be authorized in case 3rd party app calling these service
   */
  protected abstract boolean isSenderAuthorized();

  protected abstract boolean isUserLoggingOut(Context ctx);

  protected abstract boolean isUserLoggingIn(Context ctx);

  protected abstract void setUserIdInContext(Context ctx);

  protected abstract boolean isUserLoggedIn(Context ctx);

  protected abstract void setEmailInContext(Context ctx);

  protected abstract void setNicknameInContext(Context ctx);

  /**
   * user id in Context doesnt match user id in GAE (or any other framework).
   * may happen if cookie accepted/deleted selectively
   */
  protected abstract boolean isUserIdMismatch(Context ctx);

  protected abstract boolean isRequestAuthorized();

  private Context initializeContextFromCookie() {
    log.fine("Initialize end-user session");
    String sessionToken = null;
    String cookieName = ConfigService.getInstance().getSessionCookieName();
    Cookie[] ca = request.getCookies();
    if (ca != null) {
      for (Cookie c : ca) {
        if (c.getName().equals(cookieName)) {
          sessionToken = c.getValue();
          break;
        }
      }
    }
    log.info("Session Cookie: " + cookieName + "=" + sessionToken);
    Context ctx = new Context(sessionToken);
    return ctx;
  }

  private void setSessionCookie(Context ctx) {
    String sessionToken = ctx.getSessionToken();
    String cookieName = ConfigService.getInstance().getSessionCookieName();
    log.info("Setting cookie: " + sessionToken);
    Cookie cookie = new Cookie(cookieName, sessionToken);
    cookie.setMaxAge(60 * 60 * 24 * 7);
    response.addCookie(cookie);
  }

  // if domain is "localhost", redirect to "127.0.0.1"
  private boolean redirectLocalHostToIP() throws IOException {
    if (request.getServerName().equals("localhost")) {
      String redirect = UrlUtil.getUrl(request.getScheme(), "127.0.0.1",
          request.getServerPort(), request.getRequestURI());
      log.info("redirecting from localhost to " + redirect);
      response.sendRedirect(redirect);
      return true;
    }
    return false;
  }

  private void logRequest() {
    if (!log.isLoggable(Level.INFO))
      return;
    log.info("============================================================");
    String q = request.getQueryString();
    q = q == null ? "" : "?" + q;
    log.info("Request URL: " + request.getRequestURL().append(q).toString());

    if (!log.isLoggable(Level.FINER))
      return;

    log.finer("servlet path: " + request.getServletPath());

    Cookie[] cookies = request.getCookies();
    if (cookies != null)
      for (Cookie c : cookies) {
        log.finer("cookie: " + c.getName() + "=" + c.getValue());
      }

    Enumeration<String> names = request.getHeaderNames();
    while (names.hasMoreElements()) {
      String name = names.nextElement();
      log.finest("header: " + name + "=" + request.getHeader(name));
    }
  }

  public void init(FilterConfig filterConfig) {
    this.fc = filterConfig;
  }

  public void destroy() {
    this.fc = null;
  }
}